package com.jielin.handling.web.interceporsconfig.intercepors;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jielin.handling.Annotation.MyPower;
import com.jielin.handling.bean.AppResult;
import com.jielin.handling.redis.RedisKey;
import com.jielin.handling.redis.RedisTemplateX;
import com.jielin.handling.service.log.SysLogService;
import com.jielin.handling.util.ConstantUtil;
import com.jielin.handling.util.StringUtil;
import com.jielin.handling.web.interceporsconfig.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;

@Component
@Slf4j
public class AdminIntercepor implements HandlerInterceptor {
    @Autowired
    private RedisTemplateX redisTemplateX;
    @Autowired
    private SysLogService sysLogService;

    /**
     * request body data
     */
    private String requestBody;
    /**
     * request wrapper（包装）
     */
    private RequestWrapper requestWrapper;

    public void crossDomain(HttpServletRequest request, HttpServletResponse response) {

        response.setHeader("Access-Control-Allow-Origin", "*");// 指示是否可以共享响应。设置允许哪些域名应用进行ajax访问
        response.setHeader("Access-Control-Allow-Methods", "*");// 指定访问资源以响应预检请求时允许的方法。
        response.setHeader("Access-Control-Max-Age", "1800");// 将预检请求的结果缓存30分钟：
//		response.setHeader("Access-Control-Allow-Headers",
//		"Origin, X-Requested-With, Content-Type, Accept, Connection, User-Agent, Cookie");// 用于响应预检请求，以指示在发出实际请求时可以使用哪些HTTP标头。
//		response.setHeader("Access-Control-Allow-Credentials", "true");// 指示凭证标志为true时是否可以公开对请求的响应。
//		response.setHeader("Content-type", "application/json");
//		response.setHeader("Cache-Control", "no-cache, must-revalidate");
//		response.setCharacterEncoding("utf-8");
//		response.setContentType("application/json; charset=utf-8");

        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json; charset=utf-8");
    }

    // 这个方法是在访问接口之前执行的，我们只需要在这里写验证登陆状态的业务逻辑，就可以在用户调用指定接口之前验证登陆状态了
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        /*
         * XMLHttpRequest会遵守同源策略(same-origin policy). 也即脚本只能访问相同协议/相同主机名/相同端口的资源,
         * 如果要突破这个限制, 那就是所谓的跨域, 此时需要遵守CORS(Cross-Origin Resource Sharing)机制。
         *
         * 那么, 允许跨域, 不就是服务端设置Access-Control-Allow-Origin: *就可以了吗? 普通的请求才是这样子的, 除此之外,
         * 还一种叫请求叫preflighted request。
         *
         * preflighted request在发送真正的请求前, 会先发送一个方法为OPTIONS的预请求(preflight request),
         * 用于试探服务端是否能接受真正的请求，如果options获得的回应是拒绝性质的，比如404\403\500等http状态，就会停止post、
         * put等请求的发出。
         *
         * 那么, 什么情况下请求会变成preflighted request呢?
         *
         * 1、请求方法不是GET/HEAD/POST
         * 2、POST请求的Content-Type并非application/x-www-form-urlencoded,
         * multipart/form-data, 或text/plain 3、请求设置了自定义的header字段
         *
         * 解决的方法:
         */
        if ("OPTIONS".equals(request.getMethod())) {
            return true;
        }

        String url = request.getRequestURL().toString();// 获取路径
        System.out.println("路径：" + url);
        if (url.contains("doLogin") || url.contains("login") || url.contains("doOut") || url.contains("logout")
                || url.contains(".css") || url.contains(".js") || url.contains(".html") || url.contains(".png")
                || url.contains(".gif") || url.contains(".ico")) {
            return true;
        }
        crossDomain(request, response);
        String token = "";

        String token1 = request.getHeader(RedisKey.ACCESS_TOKEN);

        String token2 = request.getParameter(RedisKey.ACCESS_TOKEN);

        if (!StringUtil.isEmpty(token1)) {
            token = token1;
        } else if (!StringUtil.isEmpty(token2)) {
            token = token2;
        } else {
            response.getWriter().write(JSON.toJSONString(returnMsgByCode(-9)));
            log.info("========================>token没传，请重新登录~~");
            return false;
        }

        @SuppressWarnings("all")
        List<String> pathList = (List<String>) redisTemplateX.get(token + RedisKey.POWER_LIST);

//		log.info("pathList:" + JSON.toJSONString(pathList));
        HandlerMethod handlerMethod = null;
        try {
            handlerMethod = (HandlerMethod) handler;// 获取控制器方法
        } catch (Exception e) {
            log.info("HandlerMethod类型转换出错");
            e.printStackTrace();
            // 做了一个强制类型转换。根据异常信息，在这里得到的handler是一个ResourceHttpRequestHandler，而不是HandlerMethod
            return true;
        }

        Method method = handlerMethod.getMethod();// 获取方法
        // AnnotationUtils.findAnnotation 查询方法上是否存在注解
        MyPower power = AnnotationUtils.findAnnotation(method, MyPower.class);// 获取注解

        if (power != null) {
            if (StringUtil.isEmpty(token) || CollectionUtils.isEmpty(pathList)) {// 没登入或超时
                response.getWriter().write(JSON.toJSONString(returnMsgByCode(-9)));
                log.info("========================>登录超时，请重新登录~~");
                return false;
            }
            // Token时间更新（每访问一次则更新一下Token过期时间，以最后一次30分钟后为标准）
            redisTemplateX.expire(token, ConstantUtil.USER_TOKEN_EXPIRE);
            redisTemplateX.expire(token + RedisKey.POWER_LIST, ConstantUtil.USER_TOKEN_EXPIRE);
            // 封装request
            requestWrapper = new RequestWrapper(request);
            // 获取@RequestBody注解参数和post请求参数
            requestBody = requestWrapper.getBody();
            if (power.isPublic()) {// 公共方法
                return true;
            } else {
                for (String power1 : pathList) {
                    if (StringUtil.isEmpty(power1)) {
                        continue;
                    }
                    if (url.contains(power1)) {// 有这个权限
                        return true;
                    }
                }
                response.getWriter().write(JSON.toJSONString(returnMsgByCode(-8)));
                log.info("========================>没有权限访问~~");
                return false;
            }
        }
        return true;
    }

    // 对于请求是ajax请求重定向问题的处理方法
    public void redirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 获取当前请求的路径
        String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
                + request.getContextPath();
        // 如果request.getHeader("X-Requested-With") 返回的是"XMLHttpRequest"说明就是ajax请求，需要特殊处理
        // 否则直接重定向就可以了
        System.out.println(request.getHeader("X-Requested-With"));
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            // 告诉ajax我是重定向
            response.setHeader("REDIRECT", "REDIRECT");
            // 告诉ajax我重定向的路径
            response.setHeader("CONTENTPATH", basePath + "/admin/login");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        } else {
            response.sendRedirect(basePath + "/admin/login");
        }
    }

    /**
     * 请求处理之后进行调用，但是在视图被渲染之前（Controller方法调用之后）
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           @Nullable ModelAndView modelAndView) throws Exception {
    }

    /**
     * 在整个请求结束之后被调用，也就是在DispatcherServlet 渲染了对应的视图之后执行（主要是用于进行资源清理工作）
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) throws Exception {
        String token = "";

        String token1 = (String) request.getHeader(RedisKey.ACCESS_TOKEN);

        String token2 = request.getParameter(RedisKey.ACCESS_TOKEN);

        if (!StringUtil.isEmpty(token1)) {
            token = token1;
        } else if (!StringUtil.isEmpty(token2)) {
            token = token2;
        } else {
            return;
        }

        String policeId = (String) redisTemplateX.get(token);
        if (StringUtil.isEmpty(policeId)) {
            return;
        }

        HandlerMethod handlerMethod = null;
        try {
            // 获取控制器方法
            handlerMethod = (HandlerMethod) handler;
        } catch (Exception e) {
            log.info("HandlerMethod类型转换出错");
            e.printStackTrace();
            return;
        }
        // 获取方法
        try {
            Method method = handlerMethod.getMethod();
            // AnnotationUtils.findAnnotation 查询方法上是否存在注解
            // 获取注解
//			SysLog operation = AnnotationUtils.findAnnotation(method, SysLog.class);
            MyPower operation = AnnotationUtils.findAnnotation(method, MyPower.class);// 获取注解
            if (operation == null) {
                return;
            }

            // 记录日志
            sysLogService.addSysLog(com.jielin.handling.entity.log.SysLog.builder().id(StringUtil.generatorUUID()).
                    method(request.getRequestURL().toString()).ip(request.getRemoteAddr()).policeId(policeId).operation(operation.name()).
                    params(JSONObject.toJSONString(request.getParameterMap()) + requestBody).build());
        } catch (Exception e) {
            log.info("handlerMethod.getMethod()出错！");
            e.printStackTrace();
            return;
        }
    }

    /**
     * -8:没有权限；-9:登录超时；
     *
     * @param code 状态码
     * @return
     */
    private AppResult returnMsgByCode(int code) {
        JSONObject data = new JSONObject();
        AppResult result = null;
        switch (code) {
            case -8:
                data.put("value", "没有权限~~");
                result = AppResult.builder().setCode(code).setMsg("没有权限~~").setData(data);
                break;
            case -9:
                data.put("value", "登录超时，请重新登录(即将跳转到登录页面)~~");
                result = AppResult.builder().setCode(code).setMsg("登录超时，请重新登录(即将跳转到登录页面)~~").setData(data);
                break;
            default:
                break;
        }
        return result;
    }
}
